/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.tnmk.common.util;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Map;
import java.util.logging.Level;
import org.apache.log4j.Logger;

/**
 *
 * @author Administrator
 */
public class BeanUtils {

  private final static Logger log = Logger.getLogger(BeanUtils.class);

  /**
   * @param fromObj
   * @param toObj
   * @param valFilter the filter for value of properties in fromObj
   */
  public static <S, D extends S> void copyProperties(S fromObj, D toObj, Filter valFilter) {

    if (fromObj == null || toObj == null) {
      return;
    }
    Class<? extends Object> fromClass = fromObj.getClass();
    Class<? extends Object> toClass = toObj.getClass();
    PropertyDescriptor[] toPds = null;
    PropertyDescriptor[] fromPds = null;
    PropertyDescriptor fromPd = null;
    PropertyDescriptor toPd = null;
    Object val = null;
    try {
      BeanInfo fromBean = Introspector.getBeanInfo(fromClass);
      BeanInfo toBean = Introspector.getBeanInfo(toClass);

      toPds = toBean.getPropertyDescriptors();
      fromPds = fromBean.getPropertyDescriptors();

      for (int i = 0; i < fromPds.length; i++) {
        fromPd = fromPds[i];
        if (fromPd.getName().equals("class")) {
          continue;
        }
        int findIndex = findPropertyDescriptor(toPds, fromPd.getName());
        if (findIndex < 0) {
          continue;
        }
        toPd = toPds[findIndex];
        if (!toPd.getPropertyType().isAssignableFrom(fromPd.getPropertyType())) {
          continue;
        }
        val = fromPd.getReadMethod().invoke(fromObj);
        if (valFilter == null || valFilter.accept(val)) {
          toPd.getWriteMethod().invoke(toObj, val);
        }
      }
    } catch (Exception ex) {
      String msg = "Cannot copy properties:"
              + "\n\t fromObj:" + fromObj
              + "\n\t toObj:" + toObj
              + "\n\t fromPd:" + (fromPd != null ? fromPd.getName() : null)
              + "\n\t toPd:" + (toPd != null ? toPd.getName() : null)
              + "\n\t val:" + val;
      log.error(msg, ex);
    }
  }

  public static void copyProperties(Object fromObj, Object toObj) {
    copyProperties(fromObj, toObj, null);
  }

  public static int findPropertyDescriptor(PropertyDescriptor[] arr, String searchName) {
    for (int i = 0; i < arr.length; i++) {
      if (arr[i].getName().equals(searchName)) {
        return i;
      }
    }
    return -1;
  }

  public static Object getValue(Object obj, String expression) {
    if (expression == null || expression.isEmpty()) {
      return obj;
    }
    String[] expressionParts = expression.split("\\.");
    return getValue(obj, expressionParts);
  }

  protected static Object getValue(Object obj, String[] attributes) {
    Object val = obj;
    for (String exp : attributes) {
      if (val == null) {
        return null;
      } else {
        val = getAttribute(val, exp);
      }
    }
    return val;
  }

  public static Object getAttribute(Object obj, String fieldName) {
    try {
      if (obj == null) {
        return null;
      }
      if (fieldName == null || fieldName.isEmpty()) {
        return obj;
      }
      if (obj instanceof Map) {
        Map m = (Map) obj;
        return m.get(fieldName);
      }
      BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
      PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
      for (PropertyDescriptor pd : pds) {
        if (pd.getName().equals(fieldName)) {
          return pd.getReadMethod().invoke(obj);
        }
      }
    } catch (IntrospectionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
      java.util.logging.Logger.getLogger(BeanUtils.class.getName()).log(Level.SEVERE, null, ex);
    }
    return null;
  }
  /**
   * @param obj
   * @param expression
   * @param value 
   * @return the object which new set attribute value. If expression is empty, return the new value.
   */
  public static Object setValue(Object obj, String expression, Object value) {
    if (expression == null || expression.isEmpty()) {
      return value;
    }
    String[] expressionParts = expression.split("\\.");
    Object valContainer = obj;
    if (expressionParts.length > 1) {
      String[] valContainerExpressionParts = Arrays.copyOfRange(expressionParts, 0, expressionParts.length - 1);
      valContainer = getValue(obj, valContainerExpressionParts);
    }
    setAttribute(valContainer, expressionParts[expressionParts.length - 1], value);
    return obj;
  }

  public static void setAttribute(Object obj, String fieldName, Object value) {
    try {
      if (obj == null) {
        return;
      }
      if (fieldName == null || fieldName.isEmpty()) {
        return;
      }
      if (obj instanceof Map) {
        Map m = (Map) obj;
        m.put(fieldName, value);
      }
      BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
      PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
      for (PropertyDescriptor pd : pds) {
        if (pd.getName().equals(fieldName)) {
          pd.getWriteMethod().invoke(obj, value);
        }
      }
    } catch (IntrospectionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
      java.util.logging.Logger.getLogger(BeanUtils.class.getName()).log(Level.SEVERE, null, ex);
    }
  }
}
